home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / bin / unattended-upgrade < prev    next >
Text File  |  2008-10-13  |  12KB  |  318 lines

  1. #!/usr/bin/python
  2. #
  3. # (c) 2005-2008 Canonical
  4. # Author: Michael Vogt <michael.vogt@ubuntu.com>
  5. #
  6. # Released under the GPL
  7.  
  8. import apt_inst
  9. import apt_pkg
  10.  
  11. import sys
  12. import os
  13. import string
  14. import datetime
  15.  
  16. from optparse import OptionParser
  17. from subprocess import Popen, PIPE
  18.  
  19. import warnings
  20. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  21. import apt
  22. import logging
  23. import subprocess
  24.  
  25. import gettext
  26. from gettext import gettext as _
  27.  
  28. class MyCache(apt.Cache):
  29.     def __init__(self):
  30.         apt.Cache.__init__(self)
  31.     def clear(self):
  32.         self._depcache.Init()
  33.         assert (self._depcache.InstCount == 0 and 
  34.                 self._depcache.BrokenCount == 0 and
  35.         self._depcache.DelCount == 0)
  36.         
  37.  
  38. def is_allowed_origin(pkg, allowed_origins):
  39.     if not pkg.candidateOrigin:
  40.         return False
  41.     for origin in pkg.candidateOrigin:
  42.         for allowed in allowed_origins:
  43.             if origin.origin == allowed[0] and origin.archive == allowed[1]:
  44.                 return True
  45.     return False
  46.  
  47. def check_changes_for_sanity(cache, allowed_origins, blacklist):
  48.     if cache._depcache.BrokenCount != 0:
  49.         return False
  50.     for pkg in cache:
  51.         if pkg.markedDelete:
  52.             return False
  53.         if pkg.markedInstall or pkg.markedUpgrade:
  54.             if not is_allowed_origin(pkg, allowed_origins):
  55.                 return False
  56.             if pkg.name in blacklist:
  57.                 return False
  58.     return True
  59.  
  60. def pkgname_from_deb(debfile):
  61.     # FIXME: add error checking here
  62.     control = apt_inst.debExtractControl(open(debfile))
  63.     sections = apt_pkg.ParseSection(control)
  64.     return sections["Package"]
  65.  
  66. def conffile_prompt(destFile):
  67.     logging.debug("check_conffile_prompt('%s')" % destFile)
  68.     pkgname = pkgname_from_deb(destFile)
  69.     status_file = apt_pkg.Config.Find("Dir::State::status")
  70.     parse = apt_pkg.ParseTagFile(open(status_file,"r"))
  71.     while parse.Step() == 1:
  72.         if parse.Section.get("Package") == pkgname:
  73.             logging.debug("found pkg: %s" % pkgname)
  74.             if parse.Section.has_key("Conffiles"):
  75.                 conffiles = parse.Section.get("Conffiles")
  76.                 # Conffiles:
  77.                 # /etc/bash_completion.d/m-a c7780fab6b14d75ca54e11e992a6c11c
  78.                 for line in string.split(conffiles,"\n"):
  79.                     logging.debug("conffile line: %s", line)
  80.                     l = string.split(string.strip(line))
  81.                     file = l[0]
  82.                     md5 = l[1]
  83.                     if len(l) > 2:
  84.                         obs = l[2]
  85.                     else:
  86.                         obs = None
  87.                     if os.path.exists(file) and obs != "obsolete":
  88.                         current_md5 = apt_pkg.md5sum(open(file).read())
  89.                         if current_md5 != md5:
  90.                             return True
  91.     return False
  92.  
  93.  
  94. def dpkg_conffile_prompt():
  95.     if not apt_pkg.Config.has_key("DPkg::Options"):
  96.         return True
  97.     options = apt_pkg.Config.ValueList("DPkg::Options")
  98.     for option in map(string.strip, options):
  99.         if (option == "--force-confold" or
  100.         option == "--force-confnew"):
  101.             return False
  102.     return True
  103.  
  104. def main():
  105.  
  106.     # init the options
  107.     parser = OptionParser()
  108.     parser.add_option("-d", "--debug",
  109.                       action="store_true", dest="debug", default=False,
  110.                       help=_("print debug messages"))
  111.     (options, args) = parser.parse_args()
  112.     if options.debug:
  113.         logging.getLogger().setLevel(logging.DEBUG)
  114.         pass
  115.  
  116.     #dldir = "/tmp/pyapt-test"
  117.     #try:
  118.     #    os.mkdir(dldir)
  119.     #    os.mkdir(dldir+"/partial")
  120.     #except OSError:
  121.     #    pass
  122.     #apt_pkg.Config.Set("Dir::Cache::archives",dldir)
  123.  
  124.     # format (origin, archive), e.g. ("Ubuntu","dapper-security")
  125.     allowed_origins = map(string.split, apt_pkg.Config.ValueList("Unattended-Upgrade::Allowed-Origins"))
  126.  
  127.     # pkgs that are (for some reason) not save to install
  128.     blacklisted_pkgs = apt_pkg.Config.ValueList("Unattended-Upgrade::Package-Blacklist")
  129.     logging.info(_("Initial blacklisted packages: %s"), " ".join(blacklisted_pkgs))
  130.  
  131.     logging.info(_("Starting unattended upgrades script"))
  132.  
  133.     # display available origin
  134.     logging.info(_("Allowed origins are: %s") % map(str,allowed_origins))
  135.     
  136.     # get a cache
  137.     cache = MyCache()
  138.     if cache._depcache.BrokenCount > 0:
  139.         print _("Cache has broken packages, exiting")
  140.         logging.error(_("Cache has broken packages, exiting"))
  141.         sys.exit(1)
  142.  
  143.     # find out about the packages that are upgradable (in a allowed_origin)
  144.     pkgs_to_upgrade = []
  145.     for pkg in cache:
  146.         if options.debug and pkg.isUpgradable:
  147.             logging.debug("Checking: %s (%s)" % (pkg.name,map(str, pkg.candidateOrigin)))
  148.         if (pkg.isUpgradable and 
  149.         is_allowed_origin(pkg,allowed_origins)):
  150.             try:
  151.                 pkg.markUpgrade()
  152.                 if check_changes_for_sanity(cache, allowed_origins,
  153.                                             blacklisted_pkgs):
  154.                     pkgs_to_upgrade.append(pkg)
  155.             except SystemError:
  156.                 # can't upgrade
  157.                 pass
  158.             else:
  159.                 cache.clear()
  160.                 for pkg2 in pkgs_to_upgrade:
  161.                     pkg2.markUpgrade()
  162.  
  163.     pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])
  164.     logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)
  165.            
  166.     # download what looks good
  167.     if options.debug:
  168.         fetcher = apt_pkg.GetAcquire(apt.progress.TextFetchProgress())
  169.     else:
  170.         fetcher = apt_pkg.GetAcquire()
  171.     list = apt_pkg.GetPkgSourceList()
  172.     list.ReadMainList()
  173.     recs = cache._records
  174.     pm = apt_pkg.GetPackageManager(cache._depcache)
  175.     try:
  176.         pm.GetArchives(fetcher,list,recs)
  177.     except SystemError, e:
  178.         logging.error(_("GetArchives() failed: '%s'") % e)
  179.     res = fetcher.Run()
  180.  
  181.     if dpkg_conffile_prompt():
  182.         # now check the downloaded debs for conffile conflicts and build
  183.         # a blacklist
  184.         for item in fetcher.Items:
  185.             logging.debug("%s" % item)
  186.             if item.Status == item.StatError:
  187.                 print _("An error ocured: '%s'") % item.ErrorText
  188.                 logging.error(_("An error ocured: '%s'") % item.ErrorText)
  189.             if item.Complete == False:
  190.                 print _("The URI '%s' failed to download, aborting") % item.DescURI
  191.                 logging.error(_("The URI '%s' failed to download, aborting") % item.DescURI)
  192.                 sys.exit(1)
  193.             if item.IsTrusted == False:
  194.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  195.             if conffile_prompt(item.DestFile):
  196.                 # FIXME: skip package (means to re-run the whole marking again
  197.                 # and making sure that the package will not be pulled in by
  198.                 # some other package again!
  199.                 logging.warning(_("Package '%s' has conffile prompt and needs to be upgraded manually") % pkgname_from_deb(item.DestFile))
  200.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  201.  
  202.  
  203.         # redo the selection about the packages to upgrade based on the new
  204.         # blacklist
  205.         logging.debug("blacklist: %s" % blacklisted_pkgs)
  206.         # find out about the packages that are upgradable (in a allowed_origin)
  207.         if len(blacklisted_pkgs) > 0:
  208.             cache.clear()
  209.             old_pkgs_to_upgrade = pkgs_to_upgrade[:]
  210.             pkgs_to_upgrade = []
  211.             for pkg in old_pkgs_to_upgrade:
  212.                 logging.debug("Checking (blacklist): %s" % (pkg.name))
  213.                 pkg.markUpgrade()
  214.                 if check_changes_for_sanity(cache, allowed_origins,
  215.                                             blacklisted_pkgs):
  216.                      pkgs_to_upgrade.append(pkg)
  217.                 else:
  218.                     logging.info(_("package '%s' not upgraded") % pkg.name)
  219.                     cache.clear()
  220.                     for pkg2 in pkgs_to_upgrade:
  221.                         pkg2.markUpgrade()
  222.     else:
  223.         logging.debug("dpkg is configured not to cause conffile prompts")
  224.  
  225.     logging.debug("InstCount=%i DelCount=%i BrokenCout=%i" % (cache._depcache.InstCount, cache._depcache.DelCount, cache._depcache.BrokenCount))
  226.  
  227.     # check what we have
  228.     if len(pkgs_to_upgrade) == 0:
  229.         logging.info(_("No packages found that can be upgraded unattended"))
  230.         sys.exit(0)    
  231.  
  232.     # do the install based on the new list of pkgs
  233.     pkgs = " ".join([pkg.name for pkg in pkgs_to_upgrade])
  234.     logging.info(_("Packages that are upgraded: %s" % pkgs))
  235.  
  236.     # set debconf to NON_INTERACTIVE, redirect output
  237.     os.putenv("DEBIAN_FRONTEND","noninteractive");
  238.     os.putenv("APT_LISTCHANGES_FRONTEND","none");
  239.     
  240.     # redirect to log
  241.     REDIRECT_INPUT = os.devnull
  242.     fd = os.open(REDIRECT_INPUT, os.O_RDWR)
  243.     os.dup2(fd,0)
  244.  
  245.     now = datetime.datetime.now()
  246.     logfile_dpkg = logdir+'unattended-upgrades-dpkg_%s.log' % now.isoformat('_')
  247.     logging.info(_("Writing dpkg log to '%s'") % logfile_dpkg)
  248.     fd = os.open(logfile_dpkg,os.O_RDWR|os.O_CREAT)
  249.     os.dup2(fd,1)
  250.     os.dup2(fd,2)
  251.     
  252.     # enable debugging
  253.     if options.debug:
  254.         apt_pkg.Config.Set("Debug::pkgDPkgPM","1")
  255.     # create a new package-manager. the blacklist may have changed
  256.     # the markings in the depcache
  257.     pm = apt_pkg.GetPackageManager(cache._depcache)
  258.     if not pm.GetArchives(fetcher,list,recs):
  259.         logging.error(_("pm.GetArchives() failed"))
  260.     # run the fetcher again (otherwise local file:// 
  261.     # URIs are unhappy (see LP: #56832)
  262.     res = fetcher.Run()
  263.     # now do the actual install
  264.     try:
  265.         res = pm.DoInstall()
  266.     except SystemError,e:
  267.         logging.error(_("Installing the upgrades failed!"))
  268.         logging.error(_("error message: '%s'") % e)
  269.         res = False
  270.                 
  271.     if res == pm.ResultFailed:
  272.         logging.error(_("dpkg returned a error! See '%s' for details") % logfile_dpkg)
  273.     else:
  274.         logging.info(_("All upgrades installed"))
  275.  
  276.     # check if we need to send a mail
  277.     email = apt_pkg.Config.Find("Unattended-Upgrade::Mail", "")
  278.     if email != "":
  279.         if not os.path.exists("/usr/bin/mail"):
  280.             logging.error(_("No '/usr/bin/mail', can not send mail. "
  281.                             "You probably want to install the 'mailx' package."))
  282.             return
  283.         logging.debug("Sending mail with '%s' to '%s'" % (logfile_dpkg, email))
  284.         mail = subprocess.Popen(["/usr/bin/mail",
  285.                                  "-s","unattended-upgrades result",email],
  286.                                 stdin=subprocess.PIPE)
  287.         s = _("Unattended upgrade returned: %s\n\n") % (res != pm.ResultFailed)
  288.         s += open(logfile_dpkg).read()
  289.         mail.stdin.write(s)
  290.         mail.stdin.close()
  291.         ret = mail.wait()
  292.         logging.debug("mail returned: %s" % ret)
  293.  
  294.  
  295. if __name__ == "__main__":
  296.     localesApp="unattended-upgrades"
  297.     localesDir="/usr/share/locale"
  298.     gettext.bindtextdomain(localesApp, localesDir)
  299.     gettext.textdomain(localesApp)
  300.  
  301.     if os.getuid() != 0:
  302.         print _("You need to be root to run this application")
  303.         sys.exit(1)
  304.     
  305.     if not os.path.exists("/var/log/unattended-upgrades"):
  306.         os.makedirs("/var/log/unattended-upgrades")
  307.  
  308.     # init the logging
  309.     logdir = apt_pkg.Config.FindDir("APT::UnattendedUpgrades::LogDir",
  310.                                     "/var/log/unattended-upgrades/")
  311.     logfile = logdir+apt_pkg.Config.Find("APT::UnattendedUpgrades::LogFile",
  312.                                          "unattended-upgrades.log")
  313.     logging.basicConfig(level=logging.INFO,
  314.                         format='%(asctime)s %(levelname)s %(message)s',
  315.                         filename=logfile)
  316.     # run the main code
  317.     main()
  318.